import fs from "node:fs";
import  path from "node:path";
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";

interface ExeInfo {
  path: string;
  size: number;
  version: string;
  modified: string;
  md5: string;
  crc32: string;
  sha1: string;
}

let CRC32_TABLE: Uint32Array | undefined;

export async function main(targetDir: string): Promise<void> {
  if (!targetDir) {
    console.error('用法：node scripts/rename_and_scan.js "D:\\folder\\path"');
    process.exit(1);
  }

  const absTarget = path.resolve(targetDir);
  if (!fs.existsSync(absTarget) || !fs.statSync(absTarget).isDirectory()) {
    console.error("指定路径不存在或不是文件夹：", absTarget);
    process.exit(1);
  }

  console.log("开始处理目录：", absTarget);

  const exeInfos: ExeInfo[] = [];
  await walkAndProcess(absTarget, exeInfos);

  // 写入文本文件（格式参考你的示例）
  const outPath = path.join(absTarget, "File_MD5.txt");
  const txtLines = exeInfos
    .map((info) =>
      [
        `文件: ${path.basename(info.path)}`,
        `大小: ${info.size} 字节`,
        `文件版本: ${info.version || ""}`,
        `修改时间: ${info.modified}`,
        `MD5: ${info.md5}`,
        `SHA1: ${info.sha1}`,
        `CRC32: ${info.crc32}`,
        ``, // 分隔
      ].join("\n")
    );
  fs.writeFileSync(outPath, txtLines.join("\n"), "utf-8");
  console.log("完成。结果已写入：", outPath);
}

async function walkAndProcess(dir: string, exeInfos: ExeInfo[]): Promise<void> {
  const entries = fs.readdirSync(dir, { withFileTypes: true });
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);

    if (entry.isDirectory()) {
      await walkAndProcess(fullPath, exeInfos);
      continue;
    }

    // 重命名：移除文件名中的 `_beta`
    const renamedPath = renameFileIfContainsBeta(fullPath);

    // 如果是 .exe 文件，收集信息
    if (renamedPath.toLowerCase().endsWith(".exe")) {
      const info = await collectExeInfo(renamedPath);
      exeInfos.push(info);
      console.log("收集：", info.path);
    }
  }
}

function renameFileIfContainsBeta(filePath: string): string {
  const dir = path.dirname(filePath);
  const name = path.basename(filePath);
  const newName = name.replace(/_beta/gi, "");
  if (newName === name) {
    return filePath;
  }

  let target = path.join(dir, newName);
  if (fs.existsSync(target)) {
    const { name: baseName, ext } = splitNameExt(newName);
    let i = 1;
    while (fs.existsSync(target)) {
      target = path.join(dir, `${baseName}-renamed-${i}${ext}`);
      i++;
    }
  }

  fs.renameSync(filePath, target);
  console.log(`重命名：${name} -> ${path.basename(target)}`);
  return target;
}

function splitNameExt(filename: string): { name: string; ext: string } {
  const ext = path.extname(filename);
  const name = filename.slice(0, filename.length - ext.length);
  return { name, ext };
}

async function collectExeInfo(filePath: string): Promise<ExeInfo> {
  const stat = fs.statSync(filePath);
  const size = stat.size;
  const modified = formatDate(stat.mtime);

  let md5 = "";
  try {
    md5 = await calcMD5(filePath);
  } catch {
    md5 = "";
  }

  const crc32 = await calcCRC32(filePath);

  let sha1 = "";
  try {
    sha1 = await calcSHA1(filePath);
  } catch {
    sha1 = "";
  }

  const version = getExeVersion(filePath);

  return {
    path: filePath,
    size,
    version,
    modified,
    md5,
    crc32,
    sha1,
  };
}

function formatDate(date: Date): string {
  const Y = date.getFullYear();
  const M = pad2(date.getMonth() + 1);
  const D = pad2(date.getDate());
  const hh = pad2(date.getHours());
  const mm = pad2(date.getMinutes());
  const ss = pad2(date.getSeconds());
  return `${Y}年${M}月${D}日, ${hh}:${mm}:${ss}`;
}

function pad2(n: number): string {
  return n < 10 ? "0" + n : "" + n;
}

// 统一改为使用 calcHash，返回大写
function calcMD5(filePath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const hash = createHash("md5");
    const stream = fs.createReadStream(filePath);
    stream.on("error", reject);
    stream.on("data", (chunk) => hash.update(chunk));
    stream.on("end", () => resolve(hash.digest("hex")));
  });
}

function calcSHA1(filePath: string): Promise<string> {
  return calcHash(filePath, "SHA1");
}

// CRC32 实现（标准多项式 0xEDB88320）
function calcCRC32(filePath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const table = CRC32_TABLE || (CRC32_TABLE = buildCRC32Table());
    let crc = 0xffffffff;

    const stream = fs.createReadStream(filePath);
    stream.on("error", reject);
    stream.on("data", (buf) => {
      for (let i = 0; i < buf.length; i++) {
        const byte = buf[i];
        crc = (crc >>> 8) ^ table[(crc ^ Number(byte)) & 0xff];
      }
    });
    stream.on("end", () => {
      crc = (crc ^ 0xffffffff) >>> 0;
      const hex = crc.toString(16).toUpperCase().padStart(8, "0");
      resolve(hex);
    });
  });
}

function buildCRC32Table(): Uint32Array {
  const table = new Uint32Array(256);
  for (let i = 0; i < 256; i++) {
    let c = i;
    for (let j = 0; j < 8; j++) {
      c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
    }
    table[i] = c >>> 0;
  }
  return table;
}

// 通过 PowerShell 获取 exe 的文件版本（ProductVersion，如果没有则 FileVersion）
function getExeVersion(filePath: string): string {
  const escaped = filePath.replace(/'/g, "''");
  const ps =
    `$v = [System.Diagnostics.FileVersionInfo]::GetVersionInfo('${escaped}'); ` +
    `if($v){ if($v.ProductVersion){ $v.ProductVersion } elseif($v.FileVersion){ $v.FileVersion } }`;
  const res = spawnSync("powershell", ["-NoProfile", "-Command", ps], {
    encoding: "utf8",
    windowsHide: true,
  });

  if (res.error || res.status !== 0) {
    return "";
  }
  const output = (res.stdout || "").trim();
  return output;
}

function getHashViaPowershell(
  filePath: string,
  algorithm: "MD5" | "SHA1"
): string {
  // 使用 PowerShell 获取哈希；保证路径单引号转义，输出为大写
  const ps = `$h = Get-FileHash -Algorithm ${algorithm} -LiteralPath '${filePath.replace(/'/g, "''")}'; if($h){ $h.Hash }`;
  const res = spawnSync("powershell", ["-NoProfile", "-Command", ps], {
    encoding: "utf8",
    windowsHide: true,
  });

  if (res.error || res.status !== 0) return "";
  const output = (res.stdout || "").trim().toUpperCase();
  return output;
}

// 新增：使用 certutil 作为哈希兜底（适配旧版 PowerShell 或受限环境）
function getHashViaCertutil(
  filePath: string,
  algorithm: "MD5" | "SHA1"
): string {
  const res = spawnSync("certutil", ["-hashfile", filePath, algorithm], {
    encoding: "utf8",
    windowsHide: true,
  });
  if (res.error || res.status !== 0) return "";
  const output = (res.stdout || "").trim();
  const hexLine =
    output
      .split(/\r?\n/)
      .map((l) => l.trim())
      .find((l) => /^[0-9a-fA-F]{32,64}$/.test(l)) || "";
  return hexLine.toUpperCase();
}

// 新增：统一的哈希计算封装（PowerShell -> certutil -> Node），统一返回大写
function calcHash(
  filePath: string,
  algorithm: "MD5" | "SHA1"
): Promise<string> {
  return new Promise((resolve, reject) => {
    // 1) PowerShell Get-FileHash
    try {
      const viaPS = getHashViaPowershell(filePath, algorithm);
      if (viaPS) {
        return resolve(viaPS);
      }
    } catch {
      // ignore
    }

    // 2) certutil
    try {
      const viaCert = getHashViaCertutil(filePath, algorithm);
      if (viaCert) {
        return resolve(viaCert);
      }
    } catch {
      // ignore
    }

    // 3) Node 流式计算
    const hash = createHash(algorithm.toLowerCase());
    const stream = fs.createReadStream(filePath);
    stream.on("error", reject);
    stream.on("data", (chunk) => hash.update(chunk));
    stream.on("end", () => resolve(hash.digest("hex").toUpperCase()));
  });
}
